package top.haijunit.work.core.redis;

import cn.hutool.extra.spring.SpringUtil;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Redis 工具类，用于简化 Redis 操作。
 * 提供对 Redis 的基本操作接口，包括字符串、集合、哈希和列表等类型。
 * 使用 RedisTemplate 执行实际的 Redis 操作。
 *
 * @author zhanghaijun
 * @since 2024-08-01
 */
@Slf4j
@UtilityClass
public class RedisTemplateUtils {

    private StringRedisTemplate stringRedisTemplate;

    /**
     * 获取 RedisTemplate 实例，如果为 null 则抛出异常。
     *
     * @return RedisTemplate 实例
     * @throws RuntimeException 如果 RedisTemplate 为 null
     */
    private StringRedisTemplate getRedisTemplate() {
        if (null == stringRedisTemplate) {
            stringRedisTemplate = SpringUtil.getBean(StringRedisTemplate.class);
        }
        Assert.notNull(stringRedisTemplate, "StringRedisTemplate 未注入，请检查配置 redis 连接！");
        return stringRedisTemplate;
    }

    /**
     * 设置 key 的过期时间。
     *
     * @param key  需要设置过期时间的 key
     * @param time 过期时间，单位为秒
     * @return 如果设置成功则返回 true，否则返回 false
     */
    public Boolean expire(String key, long time) {
        return getRedisTemplate().expire(key, time, TimeUnit.SECONDS);
    }

    /**
     * 获取 key 的剩余过期时间。
     *
     * @param key 需要获取过期时间的 key
     * @return 剩余过期时间，单位为秒
     */
    public Long getExpire(String key) {
        return getRedisTemplate().getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判断 key 是否存在。
     *
     * @param key 需要检查的 key
     * @return 如果 key 存在则返回 true，否则返回 false
     */
    public Boolean hasKey(String key) {
        return getRedisTemplate().hasKey(key);
    }

    /**
     * 移除指定 key 的过期时间，变为永久存储。
     *
     * @param key 需要移除过期时间的 key
     * @return 如果移除成功则返回 true，否则返回 false
     */
    public Boolean persist(String key) {
        return getRedisTemplate().boundValueOps(key).persist();
    }

    // String 类型操作 - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    /**
     * 获取指定 key 的值。
     *
     * @param key 键
     * @return 值，如果 key 不存在则返回 null
     */
    public Object strGet(String key) {
        return key == null ? null : getRedisTemplate().opsForValue().get(key);
    }

    /**
     * 将值存入缓存。
     *
     * @param key   键
     * @param value 值
     */
    public void strSet(String key, String value) {
        getRedisTemplate().opsForValue().set(key, value);
    }

    /**
     * 将值存入缓存，并设置过期时间。
     *
     * @param key   键
     * @param value 值
     * @param time  过期时间，单位为秒，-1 表示无限期
     */
    public void strSet(String key, String value, long time) {
        if (time > 0) {
            getRedisTemplate().opsForValue().set(key, value, time, TimeUnit.SECONDS);
        } else {
            getRedisTemplate().opsForValue().set(key, value);
        }
    }

    /**
     * 批量设置多个键值对，重复的键会覆盖。
     *
     * @param keyAndValue 键值对的映射
     */
    public void strSetBatch(Map<String, String> keyAndValue) {
        getRedisTemplate().opsForValue().multiSet(keyAndValue);
    }

    /**
     * 批量设置多个键值对，只有在所有键都不存在时才会设置。
     *
     * @param keyAndValue 键值对的映射
     */
    public void strSetBatchIfAbsent(Map<String, String> keyAndValue) {
        getRedisTemplate().opsForValue().multiSetIfAbsent(keyAndValue);
    }

    /**
     * 对指定 key 的值进行加法操作。如果 key 不存在，将创建一个新 key。
     *
     * @param key    键
     * @param number 增加的数值
     * @return 操作后的值
     */
    public Long increment(String key, long number) {
        return getRedisTemplate().opsForValue().increment(key, number);
    }

    /**
     * 对指定 key 的值进行加法操作，如果 key 不存在，将创建一个新 key。
     *
     * @param key    键
     * @param number 增加的数值
     * @return 操作后的值
     */
    public Double increment(String key, double number) {
        return getRedisTemplate().opsForValue().increment(key, number);
    }

    // Set 类型操作 - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    /**
     * 将值添加到 set 缓存中。
     *
     * @param key   键
     * @param value 值
     */
    public void setAdd(String key, String value) {
        getRedisTemplate().opsForSet().add(key, value);
    }

    /**
     * 获取指定 set 中的所有成员。
     *
     * @param key 键
     * @return set 中的所有成员
     */
    public Set<String> setGet(String key) {
        return getRedisTemplate().opsForSet().members(key);
    }

    /**
     * 随机获取 set 中的指定个数的元素。
     *
     * @param key   键
     * @param count 元素个数
     * @return 随机获取的元素
     */
    public List<String> setGetRandom(String key, long count) {
        return getRedisTemplate().opsForSet().randomMembers(key, count);
    }

    /**
     * 随机获取 set 中的一个元素。
     *
     * @param key 键
     * @return 随机获取的元素
     */
    public Object setGetRandom(String key) {
        return getRedisTemplate().opsForSet().randomMember(key);
    }

    /**
     * 从 set 中弹出一个元素。
     *
     * @param key 键
     * @return 被弹出的元素
     */
    public Object setPop(String key) {
        return getRedisTemplate().opsForSet().pop(key);
    }

    /**
     * 获取 set 的大小。
     *
     * @param key 键
     * @return set 的大小
     */
    public Long setSize(String key) {
        return getRedisTemplate().opsForSet().size(key);
    }

    /**
     * 判断 set 中是否包含指定的元素。
     *
     * @param key   键
     * @param value 元素
     * @return 如果包含返回 true，否则返回 false
     */
    public Boolean setHasKey(String key, Object value) {
        return getRedisTemplate().opsForSet().isMember(key, value);
    }

    /**
     * 将元素从一个 set 移动到另一个 set。
     *
     * @param key     源 set 键
     * @param value   元素
     * @param destKey 目标 set 键
     * @return 如果移动成功则返回 true，否则返回 false
     */
    public Boolean setMove(String key, String value, String destKey) {
        return getRedisTemplate().opsForSet().move(key, value, destKey);
    }

    /**
     * 批量移除 set 中的元素。
     *
     * @param key    键
     * @param values 元素
     */
    public void setRemove(String key, Object... values) {
        getRedisTemplate().opsForSet().remove(key, values);
    }

    /**
     * 获取两个 set 之间的差集。
     *
     * @param key     set1 键
     * @param destKey set2 键
     * @return set1 与 set2 的差集
     */
    public Set<String> setDifference(String key, String destKey) {
        return getRedisTemplate().opsForSet().difference(key, destKey);
    }

    // Hash 类型操作 - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    /**
     * 将多个键值对添加到 hash 中。
     *
     * @param key 键
     * @param map 键值对映射
     */
    public void hashAdd(String key, Map<String, String> map) {
        getRedisTemplate().opsForHash().putAll(key, map);
    }

    /**
     * 获取 hash 中的所有键值对。
     *
     * @param key 键
     * @return hash 中的所有键值对
     */
    public Map<Object, Object> hashGet(String key) {
        return getRedisTemplate().opsForHash().entries(key);
    }

    /**
     * 判断指定 hash 中是否包含某个键。
     *
     * @param key     hash 键
     * @param hashKey hash 中的键
     * @return 如果包含则返回 true，否则返回 false
     */
    public boolean hashHasKey(String key, String hashKey) {
        return getRedisTemplate().opsForHash().hasKey(key, hashKey);
    }

    /**
     * 获取指定 hash 中的值（字符串）。
     *
     * @param key  hash 键
     * @param key2 hash 中的键
     * @return 值的字符串形式
     */
    public String hashGetToString(String key, String key2) {
        return Optional.ofNullable(getRedisTemplate().opsForHash().get(key, key2)).map(Objects::toString).orElse("");
    }

    /**
     * 获取指定 hash 中的值（整数）。
     *
     * @param key  hash 键
     * @param key2 hash 中的键
     * @return 值的整数形式
     */
    public Integer hashGetInt(String key, String key2) {
        return (Integer) getRedisTemplate().opsForHash().get(key, key2);
    }

    /**
     * 从 hash 中删除指定的键。
     *
     * @param key      hash 键
     * @param hashKeys 要删除的键
     * @return 删除成功的数量
     */
    public Long hashDelete(String key, Object... hashKeys) {
        return getRedisTemplate().opsForHash().delete(key, hashKeys);
    }

    /**
     * 对指定 hash 中的某个键进行增减操作。
     *
     * @param key     hash 键
     * @param hashKey hash 中的键
     * @param number  增减的数值
     * @return 操作后的值
     */
    public Long hashIncrement(String key, String hashKey, long number) {
        return getRedisTemplate().opsForHash().increment(key, hashKey, number);
    }

    /**
     * 对指定 hash 中的某个键进行增减操作（浮点数）。
     *
     * @param key     hash 键
     * @param hashKey hash 中的键
     * @param number  增减的浮点数值
     * @return 操作后的值
     */
    public Double hashIncrement(String key, String hashKey, Double number) {
        return getRedisTemplate().opsForHash().increment(key, hashKey, number);
    }

    /**
     * 获取指定 hash 中的所有键。
     *
     * @param key hash 键
     * @return hash 中的所有键
     */
    public Set<Object> hashHasKeys(String key) {
        return getRedisTemplate().opsForHash().keys(key);
    }

    /**
     * 获取指定 hash 中的键值对数量。
     *
     * @param key hash 键
     * @return 键值对数量
     */
    public Long hashSize(String key) {
        return getRedisTemplate().opsForHash().size(key);
    }

    // List 类型操作 - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    /**
     * 在列表的左边添加元素。
     *
     * @param key   列表键
     * @param value 元素值
     */
    public void listPushLeft(String key, String value) {
        getRedisTemplate().opsForList().leftPush(key, value);
    }

    /**
     * 将指定值插入到列表中某个元素前面。
     *
     * @param key   列表键
     * @param pivot 参照元素
     * @param value 要插入的值
     */
    public void listPushLeft(String key, String pivot, String value) {
        getRedisTemplate().opsForList().leftPush(key, pivot, value);
    }

    /**
     * 获取列表中指定位置的元素。
     *
     * @param key   列表键
     * @param index 索引
     * @return 指定位置的元素
     */
    public Object listIndex(String key, long index) {
        return getRedisTemplate().opsForList().index(key, index);
    }

    /**
     * 获取列表中指定范围的元素。
     *
     * @param key   列表键
     * @param start 起始索引
     * @param end   结束索引
     * @return 指定范围的元素列表
     */
    public List<String> listRange(String key, long start, long end) {
        return getRedisTemplate().opsForList().range(key, start, end);
    }

    /**
     * 向列表的左边批量添加元素。
     *
     * @param key    列表键
     * @param values 元素值
     */
    public void listPushLeftAll(String key, String... values) {
        getRedisTemplate().opsForList().leftPushAll(key, values);
    }

    /**
     * 向列表的右边添加元素。
     *
     * @param key   列表键
     * @param value 元素值
     */
    public void listPushRight(String key, String value) {
        getRedisTemplate().opsForList().rightPush(key, value);
    }

    /**
     * 向列表的右边批量添加元素。
     *
     * @param key    列表键
     * @param values 元素值
     */
    public void listPushRightAll(String key, String... values) {
        getRedisTemplate().opsForList().rightPushAll(key, values);
    }

    /**
     * 向存在的列表中右边添加元素，如果列表不存在则不执行。
     *
     * @param key   列表键
     * @param value 元素值
     */
    public void listPushRightIfPresent(String key, String value) {
        getRedisTemplate().opsForList().rightPushIfPresent(key, value);
    }

    /**
     * 获取列表的长度。
     *
     * @param key 列表键
     * @return 列表长度
     */
    public Long listSize(String key) {
        return getRedisTemplate().opsForList().size(key);
    }

    /**
     * 移除列表中左边的第一个元素。
     *
     * @param key 列表键
     */
    public void listPopLeft(String key) {
        getRedisTemplate().opsForList().leftPop(key);
    }

    /**
     * 移除列表中左边的第一个元素，设置超时时间。
     *
     * @param key     列表键
     * @param timeout 超时时间
     * @param unit    时间单位
     */
    public void listPopLeft(String key, long timeout, TimeUnit unit) {
        getRedisTemplate().opsForList().leftPop(key, timeout, unit);
    }

    /**
     * 移除列表中右边的第一个元素。
     *
     * @param key 列表键
     */
    public void listPopRight(String key) {
        getRedisTemplate().opsForList().rightPop(key);
    }

    /**
     * 移除列表中右边的第一个元素，设置超时时间。
     *
     * @param key     列表键
     * @param timeout 超时时间
     * @param unit    时间单位
     */
    public void listPopRight(String key, long timeout, TimeUnit unit) {
        getRedisTemplate().opsForList().rightPop(key, timeout, unit);
    }
}
